/***************************************************************************
 *   GLSL Test (20.08.2009)                                                *
 *   Copyright (C) 2009 by cmaster.matso (Maciej Kadłubowski)              *
 *   cmaster.matso@gmail.com                                               *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <MGPI/mgpi.h>

/* Constants */
#define SCREEN_WIDTH  800
#define SCREEN_HEIGHT 600

/* STATIC Global Variables */
static Roeskva_t engine;
static MGPI_Handle husr, hcamera;
static MGPI_Container *container;
static float xangle = -30.0f, yangle = 0.0f;
static float lx = 0.0f, ly = 0.0f, lz = 1.0f;
static int tex_mode = 0;

static float4 vertices[4] = {{-1.0f, 1.0f, 0.0f, 1.0f}, {1.0f, 1.0f, 0.0f, 1.0f}, {1.0f, -1.0f, 0.0f, 1.0f}, {-1.0f, -1.0f, 0.0f, 1.0f}};
static float2 texcoords[4] = {{0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f}, {0.0f, 0.0f}};
static float3 tangents[4] = {{1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}};
static float3 binormals[4] = {{0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}};
static float3 normals[4] = {{0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}};

static const char *normal_map_file = "../../../../../data/textures/brickbump.tga";
static const char *base_tex_file = "../../../../../data/textures/brickbump.png";
static const char *normal_map = "normalMap";
static const char *base_tex = "base";

static const char *vert_src_light, *frag_src_light;
static GLuint light_effect[2];

/* STATIC Routines Declarations */
static MGPI_BOOL key_proc(MGPI_Component *, SDL_Event *);
static MGPI_BOOL _empty(MGPI_Component *, SDL_Event *);
static void usr_trans(MGPI_RendNode *);
static int usr_rend(MGPI_RendNode *);
static void user_engine_setup(void);
static void lighting_on(MGPI_RendNode *);
static void lighting_off(MGPI_RendNode *);

/**
 * Initializes the Roeskva for work.
 */
static void init_engine(int *argc, char *argv[]) {
    float4 position = {0., 0., 2., 1.};

    engImplement(&engine);
    if (!engStart(&engine)) exit(EXIT_FAILURE);

    hcamera = engine.createCamera(MGPI_PERSPECTIVE);

    engine.setProperty(MGPI_CAMERA_POSITION, hcamera, position);
    engine.setProperty(MGPI_CAMERA_NEAR, hcamera, 0.1f);
    engine.setProperty(MGPI_CAMERA_FAR, hcamera, 100000.0f);
    engine.setProperty(MGPI_CAMERA_FOVY, hcamera, 60.0f);
    engine.setProperty(MGPI_CAMERA_VIEWPORT, hcamera, SCREEN_WIDTH, SCREEN_HEIGHT);

    engine.setup(MGPI_SETUP_USER, user_engine_setup);
    
    engine.setProperty(MGPI_SHADERS);

    SDL_WM_SetCaption("Roeskva GLSL Test", NULL);
    SDL_ShowCursor(SDL_DISABLE);

    return;
}

/**
 * Sets up OpenGL parameters used in the engine.
 */
static void user_engine_setup(void) {
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    glShadeModel(GL_SMOOTH);

    glClearDepth(1.0f);
    glDepthFunc(GL_LEQUAL);

    glEnable(GL_MULTISAMPLE_ARB);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    return;
}

/**
 * Initializes the scene.
 */
static void init_scene(void) {
    const char *names[2], *params[2];
    GLuint tex0;
    names[0] = base_tex_file;
    names[1] = normal_map_file;
    params[0] = base_tex;
    params[1] = normal_map;

    husr = engine.loadObject(NULL, MGPI_USER_DEFINED);
    if (!(tex0 = g3dTexture2DType("../../../../../data/textures/brickbump.png", GL_RGB))) exit(EXIT_FAILURE);
    
    engine.selectObject(husr);
    engine.setProperty(MGPI_TEXTURE, tex0, GL_TEXTURE_2D);

    engine.setProperty(MGPI_USER_DEFINED, usr_rend);
    engine.setObjectTransformations(husr, usr_trans);

    /*if (!(vert_src_light = (const char *) fload("../../../../../data/shaders/blinn.vert"))) exit(EXIT_FAILURE);
    if (!(frag_src_light = (const char *) fload("../../../../../data/shaders/blinn.frag"))) exit(EXIT_FAILURE);*/

    if (!(vert_src_light = (const char *) fload("../../../../../data/shaders/normalmapBlinn.vert"))) exit(EXIT_FAILURE);
    if (!(frag_src_light = (const char *) fload("../../../../../data/shaders/normalmapBlinn.frag"))) exit(EXIT_FAILURE);

    /*if (!(vert_src_light = (const char *) fload("../../../../../data/shaders/multitexture.vert"))) exit(EXIT_FAILURE);
    if (!(frag_src_light = (const char *) fload("../../../../../data/shaders/multitexture.frag"))) exit(EXIT_FAILURE);*/

    light_effect[0] = engine.setupShader(MGPI_VERTEX_SHADER, vert_src_light);
    light_effect[1] = engine.setupShader(MGPI_FRAGMENT_SHADER, frag_src_light);

    engine.selectObject(husr);
    engine.setProperty(MGPI_OBJECT_EFFECT, 2, light_effect);

    engine.setObjectGLCommands(husr, MGPI_GL_CMD_COMBINED, 3,
            MGPI_GL_CMD_TEXTURE,
            /*MGPI_GL_CMD_MULTITEXTURE,*/
            MGPI_GL_CMD_USER, lighting_on, lighting_off,
            MGPI_GL_CMD_SHADERS);

    return;
}

/**
 * Initializes the scene container (for logic processing purposes).
 */
static void init_container(void) {
    container = (MGPI_Container *) calloc(1, sizeof (MGPI_Container));
    MGPI_CollectGarbage(container);

    container->component.onEvent[MGPI_MOUSE_BUTTON_DOWN] = _empty;
    container->component.onEvent[MGPI_MOUSE_BUTTON_UP] = _empty;
    container->component.onEvent[MGPI_MOUSE_MOTION] = _empty;
    container->component.onEvent[MGPI_KEY_DOWN] = key_proc;
    container->component.onEvent[MGPI_KEY_UP] = _empty;
    return;
}

/**
 * Renders the scene.
 */
static void render_scene(void) {/* WIN32: here is some thing wrong :/ */
    engine.updateCamera(hcamera);

    /*engWaitFrame(60);*/

    engine.renderObject(husr);

    engine.renderDiagnostic();
    engine.commitChanges();

    return;
}

/**
 * Routine called at program exit.
 */
static void at_exit(void) {
    engStop(&engine);
    MGPI_Exit();
    return;
}
/**
 * Defines user defined rendering routine.
 */
static int usr_rend(MGPI_RendNode *ptr) {
    glFrontFace(GL_CW);
    glCullFace(GL_BACK);

    glBindTexture(GL_TEXTURE_2D, ptr->tex_id);

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);

    glVertexPointer(4, GL_FLOAT, 0, vertices);
    glNormalPointer(GL_FLOAT, 0, normals);
    glTexCoordPointer(2, GL_FLOAT, 0, texcoords);

    glDrawArrays(GL_QUADS, 0, 4);

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);

    return 0;
}

/**
 * Defines user rendering object transformations.
 */
static void usr_trans(MGPI_RendNode *ptr) {
    glRotatef(xangle, 1.0f, 0.0f, 0.0f);
    glRotatef(yangle, 0.0f, 1.0f, 0.0f);
    g3dDrawCoordinates();
    return;
}

/**
 * Defines lighting for the scene.
 */
static void lighting_on(MGPI_RendNode *node) {
    float4 light_color = {1.0f, 1.0f, 1.0f, 1.0f};
    float4 eye_pos = MGPI_DEFAULT_VECTOR;

    engine.setProperty(MGPI_LIGHTS);
    engine.setProperty(MGPI_GL_ON, GL_LIGHT0);
    engine.setProperty(MGPI_LIGHT_DIRECTION, GL_LIGHT0, 0.0f, 0.0f, -1.0f);
    engine.setProperty(MGPI_LIGHT_CUTOFF, GL_LIGHT0, 30.0f);
    engine.setProperty(MGPI_LIGHT_EXPONENT, GL_LIGHT0, 0.0001f);
    engine.setProperty(MGPI_LIGHT_POSITION, GL_LIGHT0, lx, ly, lz);
    engine.setProperty(MGPI_LIGHT_DIFFUSE, GL_LIGHT0, light_color[0], light_color[1], light_color[2]);
    engine.setProperty(MGPI_LIGHT_SPECULAR, GL_LIGHT0, 0.1f, 0.1f, 0.1f);
    engine.setProperty(MGPI_LIGHT_ATTENUATION, GL_LIGHT0, 0.0f, 0.0f, 0.05f);
    engine.setProperty(MGPI_MATERIAL_SPECULAR, GL_FRONT, 1.0f, 1.0f, 1.0f);
    engine.setProperty(MGPI_MATERIAL_SHININESS, GL_FRONT, 500.0f);

    g3dGetCamVect(&eye_pos, hcamera, MGPI_CAMERA_POSITION);

    engine.selectProgram(husr);
    /*engine.setVariable(MGPI_GLSL_FLOAT4, MGPI_GLSL_UNIFORM, "EyePosition", eye_pos[0], eye_pos[1], eye_pos[2], eye_pos[3]);*/
    /*engine.setVariable(MGPI_GLSL_INT1, MGPI_GLSL_UNIFORM, "TextureMode", tex_mode);*/
    engine.setVariable(MGPI_GLSL_FLOAT3, MGPI_GLSL_UNIFORM, "EyePosition", eye_pos[0], eye_pos[1], eye_pos[2]);

    return;
}

/**
 * Defines lighting for the scene.
 */
static void lighting_off(MGPI_RendNode *node) {
    engine.setProperty(MGPI_GL_OFF, GL_LIGHT0);
    engine.setProperty(MGPI_LIGHTS);
    return;
}

/**
 * Processes key events.
 */
static MGPI_BOOL key_proc(MGPI_Component *component, SDL_Event *event) {
    switch (event->key.keysym.sym) {
        case SDLK_ESCAPE: return MGPI_FALSE;

        case SDLK_UP:
            xangle -= 2.0f;
            break;

        case SDLK_DOWN:
            xangle += 2.0f;
            break;

        case SDLK_LEFT:
            yangle -= 2.0f;
            break;

        case SDLK_RIGHT:
            yangle += 2.0f;
            break;

        case SDLK_r:
            xangle = -30.0f;
            yangle = 0.0f;
            lx = 0.0f;
            ly = 0.0f;
            lz = 1.0f;
            tex_mode = 0;
            break;

        case SDLK_w:
            ly += 0.1f;
            break;

        case SDLK_s:
            ly -= 0.1f;
            break;

        case SDLK_a:
            lx -= 0.1f;
            break;

        case SDLK_d:
            lx += 0.1f;
            break;

        case SDLK_e:
            lz += 0.1f;
            break;

        case SDLK_q:
            lz -= 0.1f;
            break;

        case SDLK_m:
            tex_mode++;
            if (tex_mode == 3) tex_mode = 0;
            break;

        default: break;
    }

    return MGPI_TRUE;
}

/**
 * Does nothing.
 */
static MGPI_BOOL _empty(MGPI_Component *component, SDL_Event *event) {
    return MGPI_TRUE;
}

/**
 * Program entry point.
 */
int main(int argc, char *argv[]) {

    MGPI_Init();
    glutInit(&argc, argv);
    atexit(at_exit);

    init_engine(&argc, argv);
    init_container();
    init_scene();

    MGPI_InitEvents(MGPI_THREAD_EVENTS, container);
    while (MGPI_STATE) render_scene();

    return 0;
}
